package org.pinda.network;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;

import org.pinda.PindaConfig;
import org.pinda.network.auth.PacketHandshake;
import org.pinda.network.auth.PacketHandshakeResponse;
import org.pinda.network.util.Stream;

public class GameSocket {
	
	/**
	 * The type of connection which we will establish
	 */
	private ConnectionType connectionType;
	/**
	 * The underlying TCP socket
	 */
	private Socket socket;
	
	/**
	 * Creates a GameSocket with a server
	 * @param connectionType The type of connection
	 */
	public GameSocket(ConnectionType connectionType) {
		this.connectionType = connectionType;
	}
	
	/**
	 * Connects to a server
	 * @param ipAddress The ip adress with an optional port
	 * @throws SocketException The error message when something goes wrong
	 */
	public void connect(String ipAddress) throws SocketException {
		System.out.println("Connecting... " + ipAddress);
		socket = new Socket();
		if(ipAddress.contains(":")) {
			String[] ipData = ipAddress.split(":");
			try {
				if(ipData.length < 2) {
					throw new SocketException("Missing IP parts, Format: ip:port. Between Brackets is optional.");
				} else {
					int port = Integer.parseInt(ipData[1]);
					socket.connect(new InetSocketAddress(ipData[0], port), 5000);
					//Send connection type to server
					System.out.println("Connected to " + ipAddress);
					sendPacket(new PacketHandshake(connectionType));
					System.out.println("Waiting for handshake response...");
					PacketHandshakeResponse handshakeResponse = readPacket(1);
					if(!handshakeResponse.passedAuth()) {
						throw new IOException("Server actively refused connection");
					}
					System.out.println("Passed Handshake");
				}
			} catch (NumberFormatException e) {
				throw new SocketException("Invalid Port Number,");
			} catch (IOException e) {
				throw new SocketException("Failed to connect to server.");
			}
		} else {
			if(connectionType == ConnectionType.UPDATE || connectionType == ConnectionType.SERVER_BROWSER) {
				connect(PindaConfig.getInstance().getString("server_browser"));
			} else {
				connect(ipAddress + ":" + PindaConfig.getInstance().getInt("game_port"));
			}
		}
	}
	
	private PacketHandshakeResponse readPacket(int expectedId) throws IOException {
		IPacket packet = readPacket();
		if(packet.getId() != expectedId) {
			throw new IOException("Server actively refused connection");
		} else {
			return (PacketHandshakeResponse)packet;
		}
	}
	
	/**
	 * Reads a packet from the socket<br/>
	 * The thread will block until a packet is received
	 * @return the received packet
	 * @throws IOException
	 */
	public IPacket readPacket() throws IOException {
		DataInputStream inStream = new DataInputStream(socket.getInputStream());
		while(inStream.available() < 4) {
			PacketUtils.sleep(10);
		}
		int packetLength = inStream.readInt();
		Stream stream = new Stream(packetLength);
		while(!stream.isFull()) {
			if(inStream.available() > 0) {
				int bytesRead = inStream.read(stream.getRawBuffer(), stream.getWriteOffset(), Math.min(inStream.available(), stream.getWriteAvailable()));
				stream.addToWriteOffset(bytesRead);
			} else {
				PacketUtils.sleep(1);
			}
		}
		return PacketUtils.readPacket(connectionType, stream);
	}
	
	/**
	 * Writes a packet onto the socket
	 * @param packet the packet to be send
	 * @throws IOException when an error occurs
	 */
	public void sendPacket(IPacket packet) throws IOException {
		DataOutputStream outStream = new DataOutputStream(socket.getOutputStream());
		PacketUtils.writePacket(outStream, packet);
	}
	
	public boolean isClosed() {
		return socket.isClosed();
	}

}
